namespace C4F.DevKit.Bluetooth
{
    namespace ServiceAndDeviceDiscovery
    {
        #region Using Directives
        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Text.RegularExpressions;
        using System.Runtime.InteropServices;
        using C4F.DevKit.Bluetooth.ServiceAndDeviceDiscovery;
        using System.Net.Sockets;
        using System.IO;
        using Resources;
        #endregion

        /// <summary>
        /// Represents the Bluetooth device and contains methods, events and 
        /// properties which are required for communicating with remote 
        /// device.
        /// </summary>        

        public sealed class Device
        {
            #region Private Members

            /// <summary>
            /// Represents the address of remote device.
            /// </summary>
            BluetoothSocketAddress address;
            /// <summary>
            /// Represents the type of Bluetooth device.
            /// </summary>
            DeviceType type;
            /// <summary>
            /// Represents the name of the remote device.
            /// </summary>
            string name;
            /// <summary>
            /// Represents the queryset which contains device-specific 
            /// information.
            /// </summary>
            QuerySet wsaQuerySet;
            #endregion

            #region Constructors

            /// <summary>
            /// Initializes a device object.
            /// </summary>
            /// <param name="querySet">Contains device-specific information.
            /// </param>       
            internal Device(QuerySet querySet)
            {
                //store the queryset in a local variable
                this.wsaQuerySet = querySet;
                //extract device address
                this.address = GetAddress(wsaQuerySet);
                //extract device name
                this.name = GetName(wsaQuerySet);
                //extract device type
                this.type = GetType(wsaQuerySet);
            }

            #endregion

            #region Internal Functions
            /// <summary>
            /// Discovers devices based on the service type provided.
            /// </summary>
            /// <param name="type">Represents the type of service to be 
            /// discovered.</param>
            /// <returns>List of RemoteServices which are of the specified type.
            /// </returns>
            /// <exception cref="WinsockException">Thrown when 
            /// when a windows socket error occurs.</exception> 
            internal List<RemoteService> DiscoverServicesByType(ServiceType type)
            {
                //Discover all Services on the remote device.
                List<RemoteService> remServices = DiscoverAllServices();
                List<RemoteService> remoteServices = new List<RemoteService>();  
                //If service type matches that of the specified type then 
                //return the remote service.

                if (remServices != null && remServices.Count != 0)
                {
                    foreach (RemoteService remServ in remServices)
                    {
                        if (remServ.Type == type)
                        {                           
                            remoteServices.Add(remServ);
                        }
                    }
                }
                //return the remoteServices list
                return remoteServices;
            }

            /// <summary>
            /// Discovers services based on the service id provided where the 
            /// serviceId is in Guid format.
            /// </summary>
            /// <param name="id">Represents the service id in Guid format.</param>
            /// <returns>Remote Service whose id matches that of specified id.
            /// </returns>        
            /// <exception cref="WinsockException">Thrown when 
            /// when a windows socket error occurs.</exception> 
            internal RemoteService DiscoverServiceById(Guid id)
            {
                //Discover all Services on the remote device.
                List<RemoteService> remoteServices =
                    DiscoverAllServices();

                RemoteService remoteService = null;

                //If service id matches that of the specified id then return the 
                //remote service.
                if (remoteServices != null && remoteServices.Count != 0)
                {
                    foreach (RemoteService remServ
                                in remoteServices)
                    {
                        if (remServ.Id.ServiceIdType == ServiceIdType.TypeUuid)
                        {
                            if (remServ.Id.ServiceIdUnion.Uuid128Value == id)
                            {
                                remoteService = remServ;
                                break;
                            }
                        }
                    }
                }
                return remoteService;
            }


            /// <summary>
            /// Discovers services based on the service id provided where the 
            /// service id is in ushort format.
            /// </summary>
            /// <param name="id">Specifies the service id in ushort format
            /// .</param>
            /// <returns>Remote Service whose id matches that of specified id.
            /// </returns> 
            /// <exception cref="WinsockException">Thrown when 
            /// when a windows socket error occurs.</exception> 
            internal RemoteService DiscoverServiceById(ushort id)
            {
                //Discover all Services on the remote device.
                List<RemoteService> remoteServices =
                                DiscoverAllServices();

                RemoteService remoteService = null;

                //If service id matches that of the specified id then return the 
                //remote service.
                if (remoteServices != null && remoteServices.Count != 0)
                {
                    foreach (RemoteService remServ in
                                    remoteServices)
                    {
                        if (remServ.Id.ServiceIdType == ServiceIdType.TypeUint16)
                        {
                            if (remServ.Id.ServiceIdUnion.Uuid16Value == id)
                            {
                                remoteService = remServ;
                                break;
                            }
                        }
                    }
                }
                return remoteService;
            }

            /// <summary>
            /// Discovers services based on the service id provided where 
            /// the id is in uint format.
            /// </summary>
            /// <param name="id">Represents the service id in uint format
            /// .</param>
            /// <returns>Remote Service whose id matches that of specified id.
            /// </returns>
            /// <exception cref="WinsockException">Thrown when 
            /// when a windows socket error occurs.</exception> 
            internal RemoteService DiscoverServiceById(uint id)
            {
                //Discover all Services on the remote device.
                List<RemoteService> remoteServices =
                                            DiscoverAllServices();

                RemoteService remoteService = null;

                //If service id matches that of the specified id then return the 
                //remote service.
                if (remoteServices != null && remoteServices.Count != 0)
                {
                    foreach (RemoteService remServ in remoteServices)
                    {
                        if (remServ.Id.ServiceIdType == ServiceIdType.TypeUint32)
                        {
                            if (remServ.Id.ServiceIdUnion.Uuid32Value == id)
                            {
                                remoteService = remServ;
                                break;
                            }
                        }
                    }
                }
                return remoteService;
            }


            #endregion

            #region Private Functions

            /// <summary>
            /// Extracts the device address from the Query set structure.
            /// </summary>
            /// <param name="querySet">Contains device-specific information.
            /// </param>
            /// <returns>Device address of the remote device.</returns>

            private BluetoothSocketAddress GetAddress(QuerySet querySet)
            {
                try
                {
                    //Verify if a device address can be extracted
                    if (querySet.NumberOfCsAddrs > 0)
                    {

                        CsAddrInfo csAddrInfo = new CsAddrInfo();
                        Marshal.PtrToStructure(querySet.Buffer, csAddrInfo);

                        //Get the remote address structure
                        BluetoothSocketAddress addr = new BluetoothSocketAddress();
                        Marshal.PtrToStructure(
                            csAddrInfo.RemoteAddr.Sockaddress, addr);

                        //Get the addres family
                        addr.AddressFamily =
                                        (ushort)Marshal.ReadInt16
                                        (csAddrInfo.RemoteAddr.Sockaddress, 0);

                        //Get the remote address
                        addr.Address = (ulong)Marshal.ReadInt64(
                                    csAddrInfo.RemoteAddr.Sockaddress,
                                    BluetoothHelper.ADDRESS_OFFSET);

                        //Get the port 
                        addr.Port = (uint)Marshal.ReadInt32(
                                        csAddrInfo.RemoteAddr.Sockaddress,
                                        BluetoothHelper.CHANNEL_OFFSET);


                        addr.ServiceClassId = (Guid)Marshal.PtrToStructure(
                           (IntPtr)(csAddrInfo.RemoteAddr.Sockaddress.ToInt64()
                           + BluetoothHelper.GUID_OFFSET),
                            typeof(Guid));

                        return addr;
                    }
                    else
                    {
                        return null;
                    }
                }
                catch
                {
                    return null;
                }

            }

            /// <summary>
            /// Extracts the device type from the Query set structure.
            /// </summary>
            /// <param name="querySet">Contains device-specific information.
            /// </param>
            /// <returns>Device Type of remote device.</returns>
            private DeviceType GetType(QuerySet querySet)
            {
                try
                {
                    int serviceClassId = Marshal.ReadInt32
                                                  (querySet.ServiceClassId);
                    string serviceClassIdValue =
                                          String.Format("{0:x2}",
                                          serviceClassId);

                    //The service class id is in the format 0xSSDDdd where SS 
                    //represents for Major Service class,DD represents major 
                    //device class and dd represents minor device class.
                    //Only DDdd is accessed here

                    string deviceClassId =
                                            serviceClassIdValue.Substring(2);

                    int hexValue = int.Parse(
                                  deviceClassId,
                                  System.Globalization.NumberStyles.HexNumber);

                    type = (DeviceType)hexValue;
                    return type;
                }
                catch
                {
                    return DeviceType.UnclassifiedDevice;
                }
            }

            /// <summary>
            /// Extracts the device name from the Query set structure.
            /// </summary>
            /// <param name="querySet">Contains device-specific information.
            /// </param>
            /// <returns>Returns the name of remote device.</returns>
            private string GetName(QuerySet querySet)
            {
                //name of the device
                try
                {
                    string name = "";
                    //if ServiceInstanceName is empty then return the device address
                    //in string format.
                    if (querySet.ServiceInstanceName.Length == 0)
                    {
                        try
                        {
                            CsAddrInfo csAddrInfo = new CsAddrInfo();
                            Marshal.PtrToStructure(querySet.Buffer,
                                                    csAddrInfo);

                            IntPtr addressAsString =
                                            Marshal.AllocHGlobal(1000);

                            int addressSize = 0x1000;

                            int result = BluetoothHelper.WSAAddressToString(
                                       csAddrInfo.RemoteAddr.Sockaddress,
                                       csAddrInfo.RemoteAddr.SockaddressLength,
                                       null,
                                       addressAsString,
                                       ref addressSize);

                            BluetoothHelper.GetError();
                            string addressString = Marshal.PtrToStringAnsi(
                                                        addressAsString);

                            Marshal.FreeHGlobal(addressAsString);
                            name = addressString;
                        }
                        catch
                        {
                            name = address.Address.ToString();
                        }
                    }
                    else
                        name = Regex.Replace(querySet.ServiceInstanceName,
                                       SDPMessages.PatternForNonPrintChars,
                                       string.Empty,
                                       RegexOptions.IgnoreCase);
                    return name;

                }
                catch
                {
                    return null;
                }
            }

            /// <summary>
            /// Discovers all services advertisized by the device.
            /// </summary>
            /// <returns>Returns a list of services on the device</returns>
            /// <exception cref="WinsockException">Thrown when 
            /// when a windows socket error occurs.</exception> 
            private List<RemoteService> DiscoverAllServices()
            {
                int result = 0;
                List<RemoteService> remoteServices = new List<RemoteService>();

                // Initialize WinSock
                WsaData wsadata = new WsaData();

                result = BluetoothHelper.WSAStartup(BluetoothHelper.MakeWord(2, 2),
                                                    ref wsadata);

                if (result != 0)
                    BluetoothHelper.GetError();

                //Initialize the queryset structure with information required 
                //to query for services

                QuerySet wsaq = new QuerySet();
                wsaq.Size = Marshal.SizeOf(typeof(QuerySet));
                wsaq.NameSpace = BluetoothHelper.NS_BTH;

                CsAddrInfo csAddrInfo = new CsAddrInfo();
                Marshal.PtrToStructure(wsaQuerySet.Buffer, csAddrInfo);

                IntPtr addressAsString = Marshal.AllocHGlobal(1000);
                int addressSize = 0x1000;

                //convert address to string format
                result = BluetoothHelper.WSAAddressToString(
                                   csAddrInfo.RemoteAddr.Sockaddress,
                                   csAddrInfo.RemoteAddr.SockaddressLength,
                                   null,
                                   addressAsString,
                                   ref addressSize);

                if (result != 0)
                    BluetoothHelper.GetError();

                string addressString = Marshal.PtrToStringAnsi(addressAsString);

                wsaq.Context = addressString;
                Marshal.FreeHGlobal(addressAsString);

                //set the service class id to public browse root
                Guid serviceGuid = BluetoothHelper.PUBLIC_BROWSE_UUID;
                IntPtr ptrServiceClassId = Marshal.AllocHGlobal
                                    (Marshal.SizeOf(serviceGuid));
                Marshal.StructureToPtr(serviceGuid,
                                        ptrServiceClassId, true);

                //assign service guid to be searched
                wsaq.ServiceClassId = ptrServiceClassId;

                IntPtr hLookup = IntPtr.Zero;
                //Set the flags appropriate for search
                uint dwFlags = BluetoothHelper.LUP_FLUSHCACHE
                              | BluetoothHelper.LUP_RETURN_NAME
                              | BluetoothHelper.LUP_RETURN_TYPE
                              | BluetoothHelper.LUP_RETURN_ADDR
                              | BluetoothHelper.LUP_RETURN_BLOB
                              | BluetoothHelper.LUP_RETURN_COMMENT;

                //Initiates a client query that is constrained by the 
                //information contained within a queryset structure.

                result = BluetoothHelper.WSALookupServiceBegin(wsaq,
                                                              dwFlags,
                                                              ref hLookup);
                if (result != 0)
                    BluetoothHelper.GetError();

                while (0 == result)
                {
                    int dwBuffer = 0x10000;
                    IntPtr pBuffer = Marshal.AllocHGlobal(dwBuffer);
                    QuerySet qsResult = new QuerySet();

                    //Retrieves the requested service information.
                    result = BluetoothHelper.WSALookupServiceNext(hLookup,
                                                                  dwFlags,
                                                                  ref dwBuffer,
                                                                  pBuffer);
                    if (0 == result)
                    {
                        Marshal.PtrToStructure(pBuffer, qsResult);
                        RemoteService remoteService = new RemoteService(qsResult,
                                                                        this.address);
                        //Add the remote service to list    
                        remoteServices.Add(remoteService);
                    }
                    else
                    {
                        //Get the winsock error
                        BluetoothHelper.GetError();

                        //Free unmanaged resources
                        Marshal.FreeHGlobal(ptrServiceClassId);
                    }
                    //Free unmanaged resources
                    Marshal.FreeHGlobal(pBuffer);
                }
                //end service-lookup
                result = BluetoothHelper.WSALookupServiceEnd(hLookup);
                if (result != 0)
                    BluetoothHelper.GetError();

                // cleanup winsock
                BluetoothHelper.WSACleanup();

                if (result != 0)
                    BluetoothHelper.GetError();

                //return remote services.
                return remoteServices;
            }

            #endregion

            #region Public Properties

            /// <summary>
            /// Gets the type of device.
            /// </summary>
            public DeviceType Type
            {
                get
                {
                    return this.type;
                }

            }

            /// <summary>
            /// Gets the name of the device.
            /// </summary>
            public string Name
            {
                get
                {
                    return this.name;
                }
            }

            /// <summary>
            /// Gets the address of the device.
            /// </summary>
            public BluetoothSocketAddress Address
            {
                get
                {
                    return this.address;
                }
            }

            /// <summary>
            /// Gets a list of services on the device.
            /// </summary>
            public List<RemoteService> Services
            {
                get
                {
                    return DiscoverAllServices();

                }
            }

            #endregion
        }
    }
}

